import java.io.InputStream;
import java.io.OutputStream;
import java.rmi.UnmarshalException;

import org.apache.commons.lang3.ArrayUtils;

import proto.Complex;
import proto.Primitives;

import com.google.protobuf.ByteString;

import data.CompoundContainer;

public class ProtobufTest extends SerializationTest {
	public ProtobufTest(TestRecorder recorder, Storage storage) {
		super(recorder, storage);
		super.technique = "Protobuf";
	}

	
	@Override
	protected void testBoolWrite(boolean value, OutputStream os) throws Exception {
		Primitives.Bool type = Primitives.Bool.newBuilder().setBoolType(value).build();
		write(type.toByteArray(), os);
	}
	
	@Override
	protected void testBoolWrite(boolean[] value, OutputStream os) throws Exception {
		Primitives.Bools.Builder typeBuilder = Primitives.Bools.newBuilder();
		for (boolean v : value) {
			typeBuilder.addBoolType(v);
		}
		write(typeBuilder.build().toByteArray(), os);
	}

	@Override
	protected boolean testBoolRead(InputStream is) throws Exception {
		byte[] content = read(is);
		Primitives.Bool type = Primitives.Bool.parseFrom(content);
		return type.getBoolType();
	}

	@Override
	protected boolean[] testBoolsRead(InputStream is) throws Exception {
		byte[] content = read(is);
		Primitives.Bools type = Primitives.Bools.parseFrom(content);
		return ArrayUtils.toPrimitive(type.getBoolTypeList().toArray(new Boolean[type.getBoolTypeList().size()]));
	}

	@Override
	protected void testByteWrite(byte value, OutputStream os) throws Exception {
		byte[] v = new byte[1];
		v[0] = value;
		Primitives.Byte type = Primitives.Byte.newBuilder().setBytesType(ByteString.copyFrom(v)).build();
		write(type.toByteArray(), os);
	}

	@Override
	protected void testByteWrite(byte[] value, OutputStream os) throws Exception {
		Primitives.Byte type = Primitives.Byte.newBuilder().setBytesType(ByteString.copyFrom(value)).build();		
		write(type.toByteArray(), os);
	}

	@Override
	protected byte testByteRead(InputStream is) throws Exception {
		byte[] content = read(is);
		Primitives.Byte type = Primitives.Byte.parseFrom(content);
		return type.getBytesType().byteAt(0);
	}

	@Override
	protected byte[] testBytesRead(InputStream is) throws Exception {
		byte[] content = read(is);
		Primitives.Byte type = Primitives.Byte.parseFrom(content);
		return type.getBytesType().toByteArray();
	}

	@Override
	protected void testShortWrite(short value, OutputStream os) throws Exception {
		Primitives.Int32 type = Primitives.Int32.newBuilder().setInt32Type(value).build();
		write(type.toByteArray(), os);
	}

	@Override
	protected void testShortWrite(short[] value, OutputStream os) throws Exception {
		Primitives.Int32s.Builder typeBuilder = Primitives.Int32s.newBuilder();
		for (int i : value) {
			typeBuilder.addInt32Type(i);
		}
		write(typeBuilder.build().toByteArray(), os);
	}

	@Override
	protected short testShortRead(InputStream is) throws Exception {
		byte[] content = read(is);
		Primitives.Int32 type = Primitives.Int32.parseFrom(content);
		return (short) type.getInt32Type();
	}

	@Override
	protected short[] testShortsRead(InputStream is) throws Exception {
		byte[] content = read(is);
		Primitives.Int32s type = Primitives.Int32s.parseFrom(content);
		short[] shorts = new short[type.getInt32TypeList().size()];
		for (int i = 0; i < shorts.length; i++) {
			shorts[i] = (short) type.getInt32Type(i);
		}
		return shorts;
	}

	@Override
	protected void testIntWrite(int value, OutputStream os) throws Exception {
		Primitives.Int32 type = Primitives.Int32.newBuilder().setInt32Type(value).build();
		write(type.toByteArray(), os);
	}

	@Override
	protected void testIntWrite(int[] value, OutputStream os) throws Exception {
		Primitives.Int32s.Builder typeBuilder = Primitives.Int32s.newBuilder();
		for (int i : value) {
			typeBuilder.addInt32Type(i);
		}
		write(typeBuilder.build().toByteArray(), os);
	}

	@Override
	protected int testIntRead(InputStream is) throws Exception {
		byte[] content = read(is);
		Primitives.Int32 type = Primitives.Int32.parseFrom(content);
		return type.getInt32Type();
	}

	@Override
	protected int[] testIntsRead(InputStream is) throws Exception {
		byte[] content = read(is);
		Primitives.Int32s type = Primitives.Int32s.parseFrom(content);
		return ArrayUtils.toPrimitive(type.getInt32TypeList().toArray(new Integer[type.getInt32TypeList().size()]));
	}

	@Override
	protected void testLongWrite(long value, OutputStream os) throws Exception {
		Primitives.Int64 type = Primitives.Int64.newBuilder().setInt64Type(value).build();
		write(type.toByteArray(), os);
	}

	@Override
	protected void testLongWrite(long[] value, OutputStream os) throws Exception {
		Primitives.Int64s.Builder typeBuilder = Primitives.Int64s.newBuilder();
		for (long i : value) {
			typeBuilder.addInt64Type(i);
		}
		write(typeBuilder.build().toByteArray(), os);
	}

	@Override
	protected long testLongRead(InputStream is) throws Exception {
		byte[] content = read(is);
		Primitives.Int64 type = Primitives.Int64.parseFrom(content);
		return type.getInt64Type();
	}

	@Override
	protected long[] testLongsRead(InputStream is) throws Exception {
		byte[] content = read(is);
		Primitives.Int64s type = Primitives.Int64s.parseFrom(content);
		return ArrayUtils.toPrimitive(type.getInt64TypeList().toArray(new Long[type.getInt64TypeList().size()]));
	}

	
	@Override
	protected void testFloatWrite(float value, OutputStream os) throws Exception {
		Primitives.Float type = Primitives.Float.newBuilder().setFloatType(value).build();
		write(type.toByteArray(), os);
	}

	@Override
	protected void testFloatWrite(float[] value, OutputStream os) throws Exception {
		Primitives.Floats.Builder typeBuilder = Primitives.Floats.newBuilder();
		for (float i : value) {
			typeBuilder.addFloatType(i);
		}
		write(typeBuilder.build().toByteArray(), os);
	}

	@Override
	protected float testFloatRead(InputStream is) throws Exception {
		byte[] content = read(is);
		Primitives.Float type = Primitives.Float.parseFrom(content);
		return type.getFloatType();
	}

	@Override
	protected float[] testFloatsRead(InputStream is) throws Exception {
		byte[] content = read(is);
		Primitives.Floats type = Primitives.Floats.parseFrom(content);
		return ArrayUtils.toPrimitive(type.getFloatTypeList().toArray(new Float[type.getFloatTypeList().size()]));
	}

	
	@Override
	protected void testDoubleWrite(double value, OutputStream os) throws Exception {
		Primitives.Double type = Primitives.Double.newBuilder().setDoubleType(value).build();
		write(type.toByteArray(), os);
	}

	@Override
	protected void testDoubleWrite(double[] value, OutputStream os) throws Exception {
		Primitives.Doubles.Builder typeBuilder = Primitives.Doubles.newBuilder();
		for (double i : value) {
			typeBuilder.addDoubleType(i);
		}
		write(typeBuilder.build().toByteArray(), os);
	}

	@Override
	protected double testDoubleRead(InputStream is) throws Exception {
		byte[] content = read(is);
		Primitives.Double type = Primitives.Double.parseFrom(content);
		return type.getDoubleType();
	}

	@Override
	protected double[] testDoublesRead(InputStream is) throws Exception {
		byte[] content = read(is);
		Primitives.Doubles type = Primitives.Doubles.parseFrom(content);
		return ArrayUtils.toPrimitive(type.getDoubleTypeList().toArray(new Double[type.getDoubleTypeList().size()]));
	}


	@Override
	protected void testStringWrite(String value, OutputStream os) throws Exception {
		Primitives.String type = Primitives.String.newBuilder().setStringType(value).build();
		write(type.toByteArray(), os);
	}

	@Override
	protected void testStringWrite(String[] value, OutputStream os) throws Exception {
		Primitives.Strings.Builder typeBuilder = Primitives.Strings.newBuilder();
		for (String i : value) {
			typeBuilder.addStringType(i);
		}
		write(typeBuilder.build().toByteArray(), os);
	}

	@Override
	protected String testStringRead(InputStream is) throws Exception {
		byte[] content = read(is);
		Primitives.String type = Primitives.String.parseFrom(content);
		return type.getStringType();
	}

	@Override
	protected String[] testStringsRead(InputStream is) throws Exception {
		byte[] content = read(is);
		Primitives.Strings type = Primitives.Strings.parseFrom(content);
		String[] strings = new String[iterations];
		for (int i = 0; i < type.getStringTypeCount(); i++) {
			strings[i] = type.getStringType(i);
		}
		return strings;
	}


	private CompoundContainer transformCompound(Complex.Compound orig) {
		CompoundContainer comp = new CompoundContainer();
		comp.booleanvalue = orig.getBoolType();
		comp.bytevalue = orig.getBytesType().byteAt(0);
		comp.shortvalue = (short) orig.getShortType();
		comp.intvalue = orig.getIntType();
		comp.longvalue = orig.getLongType();
		comp.floatvalue = orig.getFloatType();
		comp.doublevalue = orig.getDoubleType();
		comp.stringvalue = orig.getStringType();
		
		if (orig.hasCompound()) {
			comp.compoundvalue = transformCompound(orig.getCompound());
		}
		
		return comp;
	}
	
	private Complex.Compound transformCompound(CompoundContainer orig) {
		byte[] byteArray = new byte[1];
		byteArray[0] = orig.bytevalue;
		Complex.Compound.Builder builder = Complex.Compound.newBuilder();
		builder
		.setBoolType(orig.booleanvalue)
		.setBytesType(ByteString.copyFrom(byteArray))
		.setShortType(orig.shortvalue)
		.setIntType(orig.intvalue)
		.setLongType(orig.longvalue)
		.setFloatType(orig.floatvalue)
		.setDoubleType(orig.doublevalue)
		.setStringType(orig.stringvalue);
		
		if (orig.compoundvalue != null) {
			builder.setCompound(transformCompound(orig.compoundvalue));
		}
		return builder.build();
	}
	
	@Override
	protected void testCompoundWrite(CompoundContainer value, OutputStream os)
			throws Exception {
		Complex.Compound type = transformCompound(value);
		write(type.toByteArray(), os);
	}

	@Override
	protected void testCompoundWrite(CompoundContainer[] value, OutputStream os)
			throws Exception {
		Complex.Compounds.Builder typeBuilder = Complex.Compounds.newBuilder();
		for (CompoundContainer i : value) {
			typeBuilder.addRepeatedCompound(transformCompound(i));
		}
		write(typeBuilder.build().toByteArray(), os);
	}


	@Override
	protected CompoundContainer testCompoundRead(InputStream is)
			throws Exception {
		byte[] content = read(is);
		Complex.Compound type = Complex.Compound.parseFrom(content);
		return transformCompound(type);
	}


	@Override
	protected CompoundContainer[] testCompoundsRead(InputStream is)
			throws Exception {
		byte[] content = read(is);
		Complex.Compounds type = Complex.Compounds.parseFrom(content);
		CompoundContainer[] comps = new CompoundContainer[iterations];
		for (int i = 0; i < type.getRepeatedCompoundCount(); i++) {
			comps[i] = transformCompound(type.getRepeatedCompound(i));
		}
		return comps;
	}


}
